package FlowGraph.Nodes;

import antlr.collections.AST;
import antlr.*;

import java.util.*;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;

import JavaGrammar.*;
import JavaGrammar.TreeScanners.*;
import SymbolTable.SymbolTable;
import SymbolTable.STLocalVariableEntry;
import FlowGraph.FlowGraphNode;
import util.Util;
import util.Line;
import FlowGraph.StatementInfo.StatementInfo;
import FlowGraph.*;
import db.ClassData;

//////////////////////////////////////////////////////////////////////////
//// MethodCallNode
/**

@author Ivan Jeukens
@version v 0.1 03/10/2001
*/
public class MethodCallNode extends FlowGraphNode {

    public MethodCallNode(AST stat) {
        super(stat, METHOD_CALL);

        _parameters = new LinkedList();
        _parameterTypes = new LinkedList();

        _statementInfo = new StatementInfo(this, stat);
    }

    ///////////////////////////////////////////////////////////////////
    ////                        public methods                     ////

    public void setCallerName(String name) {
        _callerName = name;
    }
    
    public String getCallerName() {
        return _callerName;
    }
    
    /*
    public void setCallerType(Class type) {
        _callerType = type;
    }*/
    
    public Class getCallerType() {
        return _callerType;
    }

    public void setMethodName(String name) {
        _methodName = name;
    }
    
    public String getMethodName() {
        return _methodName;
    }

    public Method getMethod() {
        return _method;
    }
    
    public Constructor getConstructor() {
        return _constructor;
    }
    
    public void setReturnType(Class type) {
        _returnType = type;
    }
    
    public Class getReturnType() {
        return _returnType;
    }
    
    public int parametersSize() {
        return _parameters.size();
    }

    public String getParameter(int index) {
        if(index < 0 || index > _parameters.size()) return null;
        return (String) _parameters.get(index);
    }

    public Iterator parametersIterator() {
        return _parameters.iterator();
    }

    public void addParameter(String name) {
        _parameters.add(name);
    }

    public Object [] getParameterTypes() {
        return _parameterTypes.toArray();
    }

    public Class getParameterType(int index) {
        return (Class) _parameterTypes.get(index);
    }

    /*
    public void addParameterType(Class type) {
        _parameterTypes.add(type);
    }*/

    /*
    public void setAssignTo(String var) {
        _assignToVar = var;
    }

    public String getAssignTo() {
        return _assignToVar;
    }*/

    public void parse(ClassData info, SymbolTable ports, SymbolTable parameters,
            SymbolTable vars) {
        MethodParser parser = new MethodParser();
        parser.addSymbolTable(_variables);
        parser.addSymbolTable(ports);
        parser.addSymbolTable(parameters);
        parser.addSymbolTable(vars);
        parser.setClassName(info.getName());
        parser.setPackage(info.getPackage());
        parser.setClassInfo(info);

        _className = info.getName();
        try {
            parser.parsemc(_absTreeNode);
        }
        catch(RecognitionException ex) {
            System.out.println("RecognitionException:");
            System.out.println(ex);
        }

        _callerName = parser.callerName;
        _methodName = parser.methodName;

        // tenho que garantir que a ordem desta lista e a mesma ordem do metodo
        _parameters = parser.parameters;
        _parametersList = parser.astList;
        
        // Se for classe interna,  tem que troca o . por $ pra dar o load
        if(info.isInner() && parser.callerType.equals(info.getName())) {
            _callerType = Util.getType(Util.innerClassLoader(info.getName()), info.getName(),
                info.getPackage(), info.getImports());
        }
        else {
            _callerType = Util.getType(parser.callerType, info.getName(),
                info.getPackage(), info.getImports());
        }

        if(_callerType == void.class) {
            try {
                ClassLoader loader = ClassLoader.getSystemClassLoader();
                _callerType = loader.loadClass(_className);
            }
            catch(ClassNotFoundException ex) {
                System.out.println(ex);
            }
        }
        
        for(int i = 0;i < parser.parameterTypes.size();i++) {
            String s = (String) parser.parameterTypes.get(i);
            Class c = Util.getType(s, info.getName(),
                info.getPackage(), info.getImports());
            _parameterTypes.add(c);
            /*
            Integer narray = (Integer) parser.nArray.get(i);
            if(narray.intValue() > 0) {
                Class parclass = (Class) _parameterTypes.get(i);
                int count = 0;
                while(count < narray.intValue()) {
                    Object obj = Array.newInstance(parclass, 0);
                    parclass = obj.getClass();
                    count++;
                }                
                _parameterTypes.set(i, parclass);
            }*/
        }

        _getMethodClass(info);

        // tem cast ou nao?
        if(_method != null) {
            if(parser.returnType != null) {
                _returnType = Util.getType(parser.returnType, info.getName(),
                 info.getPackage(), info.getImports());
            }
            else {
                _returnType = _method.getReturnType();
            }
        }

        // Quando for uma atribuicao do meu extract ...
        if(_absTreeNode.getType() == JavaTokenTypes.VARIABLE_DEF) {
            AST v = _absTreeNode.getFirstChild();
            v = v.getNextSibling();
            AST type = v.getFirstChild();
            if(type.getText().equals("nome_do_tipo")) {
                if(_returnType.isArray()) {
                    Class rt = _returnType;
                    int narray = 0;
                    while(rt.isArray()) {
                        narray++;
                        rt = rt.getComponentType();
                    }
                    StringBuffer tname = new StringBuffer(rt.getName() + " ");
                    for(int i = 0;i < narray;i++) {
                        tname.append("[]");
                    }
                    type.setText(tname.toString());
                }
                else {
                    type.setText(Util.innerClassName(_returnType.getName()));
                }
                v = v.getNextSibling();
                if(v.getType() != JavaTokenTypes.IDENT) {
                    System.out.println("MethodCallParser parser: o nome " +
                        " do novo tipo nao e um identificador");
                }
                STLocalVariableEntry entry = 
                    (STLocalVariableEntry) _variables.find(v.getText());
                entry.setType(_returnType);
                entry.setPrimitive(Util.isPrimitive(_returnType));
            }
        }
        else
        if(_absTreeNode.getType() == JavaTokenTypes.EXPR) {
            AST v = _absTreeNode.getFirstChild();
            v = v.getFirstChild();
            STLocalVariableEntry entry = 
                    (STLocalVariableEntry) _variables.find(v.getText());
            if(entry != null) {
                v = entry.getNode();
                v = v.getFirstChild();
                v = v.getNextSibling();
                AST type = v.getFirstChild();
                if(type.getText().equals("nome_do_tipo")) {
                    if(_returnType.isArray()) {
                        Class rt = _returnType;
                        int narray = 0;
                        while(rt.isArray()) {
                            narray++;
                            rt = rt.getComponentType();
                        }
                        StringBuffer tname = new StringBuffer(rt.getName() + " ");
                        for(int i = 0;i < narray;i++) {
                            tname.append("[]");
                        }
                        type.setText(tname.toString());
                    }
                    else {
                        type.setText(_returnType.getName());
                    }
                    entry.setType(_returnType);
                    entry.setPrimitive(Util.isPrimitive(_returnType));
                }
            }
        }
    }
    
    public String toString() {
        if(_callerName == null) return "Method Call";
        
        StringBuffer buf = new StringBuffer();
        //String s = getAssignTo();
        //if(s != null) {
        //    buf.append("Assign To : " + s + "\n");
       // }

        buf.append(_callerName + "(" + _callerType + 
           ") calling " + _methodName + "\n");

        Object [] par = getParameterTypes();
        for(int i = 0;i < par.length;i++) {
            buf.append("\t" + getParameter(i) + " (" +
                ((Class)par[i]).getName() + ")\n");
        }

        if(_returnType != null) {
            buf.append("returning type: " + _returnType.getName() + "\n");
        }
        buf.append("AST = " + _absTreeNode.toStringTree() + "\n");

        if(_flowGraph != null) {
            buf.append("Flowgraph = " + _flowGraph.getMethodName() + "\n");
        }

        return buf.toString();
    }

    public String statementToString() {
        StringBuffer ret = new StringBuffer();

        ret.append(_callerName + "." + _methodName + "(");
        if(_parameters.size() == 0) { 
            ret.append(")");
        }
        else {
            for(int i = 0;i < _parameters.size() - 1;i++) {
                ret.append(_parameters.get(i) + ",");
            }
            ret.append(_parameters.get(_parameters.size() - 1) + ")");
        }

        return ret.toString();
    }
    
    public void setLine(Line l) {
        _line = l;
    }
    
    public Line getLine() {
        return _line;
    }

    public StatementInfo getStatementInfo() {
        return _statementInfo;
    }

    public void setFlowGraph(FlowGraph fg) {
        _flowGraph = fg;
    }
    
    public FlowGraph getFlowGraph() {
        return _flowGraph;
    }
    
    public AST getParametersList() {
        return _parametersList;
    }
    
    ///////////////////////////////////////////////////////////////////
    ////                       private methods                     ////

    private void _getMethodClass(ClassData info) {
        if(_methodName == null) {
            // adiciona a lista de parametros o tipo da classe pai
            if(info.isInner()) {
                ClassData pc = info.getSuperClass();
                Class type;
                if(pc.isInner()) {
                    type  = Util.getType(Util.innerClassLoader(pc.getName()), pc.getName(),
                        pc.getPackage(), pc.getImports());
                }
                else {
                     type = Util.getType(pc.getName(), pc.getName(), pc.getPackage(), 
                         pc.getImports());
                }
                _parameterTypes.addFirst(type);
            }
            _method = null;
            _constructor = Util.getConstructor(_callerType, _parameterTypes);
            _returnType = null;
        }
        else {
            _constructor = null;
/*
            System.out.println("CARREGANDO: " + _methodName + 
                " (type = " + _callerType + " ) : pars: ");
            for(int i = 0;i < _parameterTypes.size();i++) {
                System.out.println("par " + i + " = " + 
                    ((Class) _parameterTypes.get(i)).getName());
            }
  */          
            _method = Util.getMethod(_callerType, _methodName, _parameterTypes);
        }
    }

    ///////////////////////////////////////////////////////////////////
    ////                      private variables                    ////

    // nome da classe do callertype
    private String _className;

    private String _callerName;
    private Class _callerType;

    private String _methodName;
    private LinkedList _parameters;
    private LinkedList _parameterTypes;
    private Class _returnType;

    //private String _assignToVar;

    private Method _method;
    private Constructor _constructor;

    // O flow graph que implementa esse metodo
    private FlowGraph _flowGraph;

    private Line _line;

    private StatementInfo _statementInfo;
    
    private AST _parametersList;
}
